In a multiprocessor system, it is often necessary to ensure coordination between the various processing devices. For example, consider the situation in which a processor A and a processor B both attempt to decrement a shared resource (e.g. the value of a person's bank balance) by 10. If the value of the person's bank balance is initially 100 then, processed correctly, the shared value should decrease to 80 (100−10−10). However, if processor A and B each get the current value at the same time, then each of the processors may perform the calculation and write back the new value (90) into shared memory simultaneously. Hence, due to the lack of coordination, one of the two transactions has been masked, or overwritten, by the other transaction.
Locks may be used in order to ensure that only a single execution agent (a processor or thread) can access a shared resource at a time. Considering the above example, the user's bank balance is a shared resource and so a lock may be applied to that data value to ensure that only one processor can read and modify that value at once. If written correctly, such locks can be used to prevent transactions from overwriting each other and to ensure that access to the shared resource is coordinated across all agents. In other words, this helps to prevent the situation in which one agent performs an operation on an old version of the shared resource. A disadvantage to using locks is in the level of granularity that is protected. For example, consider a linked list. In a linked list, each element provides a data value and a pointer to the next element in the list. It is desirable when multiple agents may seek modification access to such a linked list to lock access to the list in order to prevent the situation where one of the elements no longer points to the next element in the list—causing the list to break. One option is to provide a lock for the entire list. However, if one agent wishes to modify the head of a list and another agent wishes to modify the tail of the list, then this lock is unnecessarily restrictive—the edits made by the two agents are unlikely to interfere with each other. Another option is to provide a lock for each individual element of the list. However, this may require a large number of locks in the case of a large list, with each lock requiring its own storage in memory. Locks also have a disadvantage that they can cause deadlocks. For example, if two agents each require access to two shared data resources that are protected by locks, and each of the two agents holds a lock for one of those two shared data resources, then the system enters a deadlock. In other words, neither agent can proceed and will wait forever for the other agent to release the lock that it needs.
One solution is to use transactions rather than locks. When a transaction begins, the processor enters a special execution mode. While in this mode, the system tracks reads and writes. If there is determined to be a conflict (e.g. between multiple agents) then one of the agents will be killed off and rolled back, e.g. its changes will be undone. If no conflicts occur, then the agents continue until they are done, at which time the changes made are committed. Consequently a transaction may be thought of as a series of operations that are treated as a single atomic operation since up until the transaction is committed (a single operation) the entire transaction is invisible to other agents and may even be undone or rewound by that one agent by rolling back.
In some systems, performing transactions may require that every architectural register is saved in order for those registers to be restored to their original values in the case of a rollback. Some architectures may include a significant number of registers and hence, in such systems, it may be necessary to save the values of all of those registers. However, such an approach can be wasteful, since it may involve saving data that is never modified inside the transaction. In this case, the data is stored unnecessarily, thereby consuming both storage space in order to store the values and also time in order to physically copy the data values. Furthermore, if a rollback occurs, then all saved registers must have their values restored, even if those data values have not been modified. Hence, more time may be wasted in restoring all of the stored data values if a rollback occurs. Additionally, the power consumption associated with restoring all those stored data values in the event of a rollback is also preferably avoided.
As an alternative, it is possible for the user and/or the compiler to specify those registers that will be modified during a transaction. In this case, only the specified registers are saved and only those registers will be restored in the event of a rollback. However, it may not always be possible to predict whether or not a particular register will be used in a transaction. For example, a transaction may involve one or more calls to library functions. The user or programmer may have little or no control over such library functions and may not even be able to see how the library functions work. In such cases, the user has no way of knowing which registers must be saved when the transaction starts. Consequently, the user must either save all registers, or else proceed with the possibility that a rollback will fail as a result of an unsaved register being modified.